/* ----------------------------------------------------------------------------
 *         SAM Software Package License
 * ----------------------------------------------------------------------------
 * Copyright (c) 2014, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

/** \addtogroup dacc_module Working with DACC
 *  \ingroup peripherals_module
 * The DACC driver provides the interface to configure and use the DACC peripheral.\n
 *
 * The DACC(Digital-to-Analog Converter Controller) converts digital code to analog output.
 * The data to be converted are sent in a common register for all channels. It offers up to 2
 * analog outputs.The output voltage ranges from (1/6)ADVREF to (5/6)ADVREF.
 *
 * To Enable a DACC conversion,the user has to follow these few steps:
 * <ul>
 * <li> Select an appropriate reference voltage on ADVREF   </li>
 * <li> Configure the DACC according to its requirements and special needs,which could be
 broken down into several parts:
 * -#   Enable DACC in free running mode by clearing TRGEN in DACC_MR;
 * -#   Configure Refresh Period through setting REFRESH fields
 *      in DACC_MR; The refresh mechanism is used to protect the output analog value from
 *      decreasing.
 * -#   Enable channels and write digital code to DACC_CDR,in free running mode, the conversion
 *      is started right after at least one channel is enabled and data is written .
 </li>
 * </ul>
 *
 * For more accurate information, please look at the DACC section of the
 * Datasheet.
 *
 * Related files :\n
 * \ref DACC.c\n
 * \ref DACC.h\n
 */
/*@{*/
/*@}*/
/**
 * \file
 *
 * Implementation of Digital-to-Analog Converter Controller (DACC).
 *
 */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/

#include "chip.h"

#include <stdint.h>
#include <assert.h>

/*  DMA driver instance */
static uint32_t dacDmaTxChannel;
static LinkedListDescriporView1 dmaWriteLinkList[256];
/*----------------------------------------------------------------------------
 *        Local functions
 *----------------------------------------------------------------------------*/

/**
 * \brief Configure the DMA Channels: 0 RX.
 * Channels are disabled after configure.
 * \returns 0 if the dma channel configuration successfully; otherwise returns
 * DAC_ERROR_XXX.
 */
static uint8_t _DacConfigureDmaChannels( DacDma* pDacd )
{

    /* Driver initialize */
    XDMAD_Initialize( pDacd->pXdmad, 0 );

    XDMAD_FreeChannel( pDacd->pXdmad, dacDmaTxChannel);

    /* Allocate a DMA channel for DAC0/1 TX. */
    dacDmaTxChannel = XDMAD_AllocateChannel( pDacd->pXdmad, XDMAD_TRANSFER_MEMORY, ID_DACC);
    {
        if ( dacDmaTxChannel == XDMAD_ALLOC_FAILED ) 
        {
            return DAC_ERROR;
        }
    }
    if ( XDMAD_PrepareChannel( pDacd->pXdmad, dacDmaTxChannel )) 
        return DAC_ERROR;
    return DAC_OK;
}


/**
 * \brief Configure the DMA source and destination with Linker List mode.
 *
 * \param pBuffer Pointer to dac buffer
 * \param size length of buffer
 */

static uint8_t _Dac_configureLinkList(Dacc *pDacHw, void *pXdmad, DacCmd *pCommand)
{
    uint32_t xdmaCndc;
    sXdmadCfg xdmadCfg;
    uint32_t * pBuffer;
    /* Setup TX Link List */
    uint8_t i;
    pBuffer = (uint32_t *)pCommand->pTxBuff;
    for(i = 0; i < pCommand->TxSize; i++){
        dmaWriteLinkList[i].mbr_ubc = XDMA_UBC_NVIEW_NDV1 
            | XDMA_UBC_NDE_FETCH_EN
            | XDMA_UBC_NSEN_UPDATED
            | XDMAC_CUBC_UBLEN(4);
        dmaWriteLinkList[i].mbr_sa = (uint32_t)pBuffer;
        dmaWriteLinkList[i].mbr_da = (uint32_t)&(pDacHw->DACC_CDR[pCommand->dacChannel]);
        if ( i == (pCommand->TxSize - 1 )) {
            if (pCommand->loopback) {
                dmaWriteLinkList[i].mbr_nda = (uint32_t)&dmaWriteLinkList[0];
            }
            else {
                dmaWriteLinkList[i].mbr_nda = 0;
            }
        } else {
            dmaWriteLinkList[i].mbr_nda = (uint32_t)&dmaWriteLinkList[i+1];
        }
        pBuffer++;
    }
    xdmadCfg.mbr_cfg = XDMAC_CC_TYPE_PER_TRAN 
        | XDMAC_CC_MBSIZE_SINGLE 
        | XDMAC_CC_DSYNC_MEM2PER 
        | XDMAC_CC_CSIZE_CHK_1 
        | XDMAC_CC_DWIDTH_WORD
        | XDMAC_CC_SIF_AHB_IF0 
        | XDMAC_CC_DIF_AHB_IF1 
        | XDMAC_CC_SAM_INCREMENTED_AM 
        | XDMAC_CC_DAM_FIXED_AM 
        | XDMAC_CC_PERID(XDMAIF_Get_ChannelNumber(ID_DACC, XDMAD_TRANSFER_TX ));
    xdmaCndc = XDMAC_CNDC_NDVIEW_NDV1 
        | XDMAC_CNDC_NDE_DSCR_FETCH_EN 
        | XDMAC_CNDC_NDSUP_SRC_PARAMS_UPDATED
        | XDMAC_CNDC_NDDUP_DST_PARAMS_UPDATED ;
    XDMAD_ConfigureTransfer( pXdmad, dacDmaTxChannel, &xdmadCfg, xdmaCndc, (uint32_t)&dmaWriteLinkList[0]);
    return DAC_OK;
}

/*----------------------------------------------------------------------------
 *        Exported functions
 *----------------------------------------------------------------------------*/


/**
 * \brief Initializes the DacDma structure and the corresponding DAC & DMA hardware.
 * select value.
 * The driver will uses DMA channel 0 for RX .
 * The DMA channels are freed automatically when no DMA command processing.
 *
 * \param pDacd  Pointer to a DacDma instance.
 * \param pDacHw Associated Dac peripheral.
 * \param DacId  Dac peripheral identifier.
 * \param pDmad  Pointer to a Dmad instance. 
 */
uint32_t Dac_ConfigureDma( DacDma *pDacd ,
        Dacc *pDacHw ,
        uint8_t DacId,
        sXdmad *pXdmad )
{
    /* Initialize the Dac structure */
    pDacd->pDacHw = pDacHw;
    pDacd->dacId  = DacId;
    pDacd->semaphore = 1;
    pDacd->pCurrentCommand = 0;
    pDacd->pXdmad = pXdmad;
    return 0;
}

/**
 * \brief Starts a DAC transfer. This is a non blocking function. It will
 *  return as soon as the transfer is started.
 *
 * \param pDacd  Pointer to a DacDma instance.
 * \param pCommand Pointer to the Dac command to execute.
 * \returns 0 if the transfer has been started successfully; otherwise returns
 * DAC_ERROR_LOCK is the driver is in use, or DAC_ERROR if the command is not
 * valid.
 */
uint32_t Dac_SendData( DacDma *pDacd, DacCmd *pCommand)
{
    Dacc *pDacHw = pDacd->pDacHw;

    /* Try to get the dataflash semaphore */
    if (pDacd->semaphore == 0) {

        return DAC_ERROR_LOCK;
    }
    pDacd->semaphore--;

    // Initialize the callback
    pDacd->pCurrentCommand = pCommand;

    /* Initialize DMA controller using channel 0 for RX. */
    if (_DacConfigureDmaChannels(pDacd) )
        return DAC_ERROR_LOCK;

    if (_Dac_configureLinkList(pDacHw, pDacd->pXdmad, pCommand))
        return DAC_ERROR_LOCK;


    /* Start DMA TX */
    if (XDMAD_StartTransfer( pDacd->pXdmad, dacDmaTxChannel )) 
        return DAC_ERROR_LOCK;
    return DAC_OK;;
}



#if 0

/*  DMA driver instance */
static uint32_t dacDmaTxChannel;

/** Global DMA driver for all transfer */
static sXdmad dacDma;

#define MAX_LINKER_LIST      256
/** DMA Linker list descriptors */
static LinkedListDescriporView1 dmaWriteLinkList[MAX_LINKER_LIST];

/*----------------------------------------------------------------------------
 *        Exported functions
 *----------------------------------------------------------------------------*/
/**
 * Set the Conversion Data
 * \param pDACC Pointer to an Dacc instance.
 * \param dwData  date to be converted.
 * \param channel Channel number
 */
extern void DACC_SetConversionData( Dacc* pDACC, uint32_t dwData, uint32_t channel)
{
    pDACC->DACC_CDR[channel] = dwData ;
}

/**
 * \brief Configure the DMA TX Channel .
 * Channels are disabled after configure.
 * \returns 0 if the dma channel configuration successfully; otherwise returns
 * UARTD_ERROR_XXX.
 */
uint8_t dacConfigureDmaChannel(void)
{

    /* Driver initialize */
    XDMAD_Initialize( &dacDma, 0 );

    /* Allocate a DMA channel for DAC TX. */
    dacDmaTxChannel = XDMAD_AllocateChannel( &dacDma, XDMAD_TRANSFER_MEMORY, ID_DACC);
    {
        if ( dacDmaTxChannel == XDMAD_ALLOC_FAILED ) 
        {
            return UARTD_ERROR;
        }
    }
    if ( XDMAD_PrepareChannel( &dacDma, dacDmaTxChannel ))
        return UARTD_ERROR;
    return 0;
}

/**
 * \brief Configure the DMA source and destination with Linker List mode.
 *
 * \param pBuffer Pointer to DAC buffer
 * \param len length of buffer
 * \param loopback 1: loopback, 0: fixed length of LLI
 * \param channel Channel number
 */
uint8_t dacConfigureLinkList(Dacc *pDACC, uint32_t *pBuffer, uint32_t len, uint32_t loopback, uint32_t channel)
{
    uint8_t i;
    uint32_t xdmaCndc;
    sXdmadCfg xdmadCfg;
    assert (len < MAX_LINKER_LIST);
    for(i = 0; i < len; i++){
        dmaWriteLinkList[i].mbr_ubc = XDMA_UBC_NVIEW_NDV1 
            | XDMA_UBC_NDE_FETCH_EN
            | XDMA_UBC_NSEN_UPDATED
            | XDMAC_CUBC_UBLEN(4);
        dmaWriteLinkList[i].mbr_sa = (uint32_t)(pBuffer);
        dmaWriteLinkList[i].mbr_da = (uint32_t)&(pDACC->DACC_CDR[channel]);
        if ( i == (len - 1 )) {
            if (loopback) {
                dmaWriteLinkList[i].mbr_nda = (uint32_t)&dmaWriteLinkList[0];
            }
            else {
                dmaWriteLinkList[i].mbr_nda = 0;
            }
        } else {
            dmaWriteLinkList[i].mbr_nda = (uint32_t)&dmaWriteLinkList[i+1];
        }
        pBuffer++;
    }
    xdmadCfg.mbr_cfg = XDMAC_CC_TYPE_PER_TRAN 
        | XDMAC_CC_MBSIZE_SINGLE 
        | XDMAC_CC_DSYNC_MEM2PER 
        | XDMAC_CC_CSIZE_CHK_1 
        | XDMAC_CC_DWIDTH_WORD
        | XDMAC_CC_SIF_AHB_IF0 
        | XDMAC_CC_DIF_AHB_IF1 
        | XDMAC_CC_SAM_INCREMENTED_AM 
        | XDMAC_CC_DAM_FIXED_AM 
        | XDMAC_CC_PERID(XDMAIF_Get_ChannelNumber(ID_DACC, XDMAD_TRANSFER_TX ));
    xdmaCndc = XDMAC_CNDC_NDVIEW_NDV1 
        | XDMAC_CNDC_NDE_DSCR_FETCH_EN 
        | XDMAC_CNDC_NDSUP_SRC_PARAMS_UPDATED
        | XDMAC_CNDC_NDDUP_DST_PARAMS_UPDATED ;
    XDMAD_ConfigureTransfer( &dacDma, dacDmaTxChannel, &xdmadCfg, xdmaCndc, (uint32_t)&dmaWriteLinkList[0]);
    return 0;
}

/**
 * \brief Start DMA transfer.
 *
 * \param channel Channel number
 */
void dacStartConvert(Dacc *pDACC, uint32_t channel)
{
    XDMAD_StartTransfer( &dacDma, dacDmaTxChannel);

}

#endif
